// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import <XCTest/XCTest.h>

#import "../../src/private/MDCBottomNavigationBar+Private.h"
#import "../../src/private/MDCBottomNavigationItemView.h"
#import "MaterialAvailability.h"
#import "MaterialBottomNavigation.h"
#import "MDCBottomNavigationBar+Private.h"
#import "MaterialPalettes.h"
#import "MaterialShadowElevations.h"

static NSString *const kTestItemTitleText = @"Title";

/**
 A testing MDCBottomNavigationBar that allows safeAreaInsets to be set programmatically.
 */
@interface MDCSafeAreaCustomizingBottomNavigationBar : MDCBottomNavigationBar
/** Set this to override the value returned by @c safeAreaInsets. */
@property(nonatomic, assign) UIEdgeInsets test_safeAreaInsets;
@end

@implementation MDCSafeAreaCustomizingBottomNavigationBar

- (UIEdgeInsets)safeAreaInsets {
  return self.test_safeAreaInsets;
}

@end

@interface MDCBottomNavigationBar (Testing)
@property(nonatomic, strong) NSMutableArray<MDCBottomNavigationItemView *> *itemViews;
@end

@interface MDCBottomNavigationItemView (Testing)
@property(nonatomic, strong) UILabel *label;
@end

@interface MDCBottomNavigationBarTests : XCTestCase
@property(nonatomic, strong) MDCBottomNavigationBar *bottomNavBar;
@end

@implementation MDCBottomNavigationBarTests

- (void)setUp {
  self.bottomNavBar = [[MDCBottomNavigationBar alloc] init];
}

- (void)tearDown {
  self.bottomNavBar = nil;
}

- (void)testDefaultValues {
  // When
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];

  // Then
  XCTAssertEqualObjects(bar.backgroundColor, UIColor.whiteColor);
  XCTAssertFalse(bar.isBackgroundBlurEnabled);
  XCTAssertEqual(bar.backgroundBlurEffectStyle, UIBlurEffectStyleExtraLight);
  XCTAssertEqualWithAccuracy(self.bottomNavBar.elevation, MDCShadowElevationBottomNavigationBar,
                             0.001);
  XCTAssertEqualWithAccuracy(self.bottomNavBar.mdc_currentElevation, self.bottomNavBar.elevation,
                             0.001);
  XCTAssertLessThan(self.bottomNavBar.mdc_overrideBaseElevation, 0);
  XCTAssertNil(self.bottomNavBar.mdc_elevationDidChangeBlock);
  XCTAssertEqualObjects(self.bottomNavBar.itemBadgeTextColor, UIColor.whiteColor);
  XCTAssertEqualObjects(self.bottomNavBar.itemBadgeBackgroundColor, MDCPalette.redPalette.tint700);
}

#pragma mark - Fonts

- (void)testItemTitleFontDefault {
  // Then
  XCTAssertNotNil(self.bottomNavBar.itemTitleFont);
}

- (void)testItemTitleFontAppliesToNewItems {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];

  // When
  self.bottomNavBar.itemTitleFont = [UIFont systemFontOfSize:31];
  self.bottomNavBar.items = @[ item1, item2 ];

  // Then
  for (MDCBottomNavigationItemView *item in self.bottomNavBar.itemViews) {
    XCTAssertEqualObjects(item.itemTitleFont, self.bottomNavBar.itemTitleFont);
  }
}

- (void)testItemTitleFontAppliesToExistingItems {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.itemTitleFont = [UIFont systemFontOfSize:31];

  // Then
  for (MDCBottomNavigationItemView *item in self.bottomNavBar.itemViews) {
    XCTAssertEqualObjects(item.itemTitleFont, self.bottomNavBar.itemTitleFont);
  }
}

- (void)testItemReset {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.items = @[ item1, item2, item3 ];

  // Then
  NSUInteger tabsCount = 3;
  XCTAssertEqual(self.bottomNavBar.itemViews.count, tabsCount);
}

- (void)testFramesAfterReset {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:1];
  UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:2];
  [self.bottomNavBar sizeToFit];
  self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.items = @[ item1, item2, item3 ];
  [self.bottomNavBar layoutIfNeeded];

  // Then
  XCTAssertFalse(CGRectEqualToRect(self.bottomNavBar.itemViews[0].frame, CGRectZero));
}

- (void)testSelectedItemAfterReset {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.items = @[ item1, item2, item3 ];

  // Then
  XCTAssertNil(self.bottomNavBar.selectedItem);
}

- (NSInteger)countOfViewsWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier
                                        fromRootView:(UIView *)view {
  NSInteger sum = [view.accessibilityIdentifier isEqualToString:accessibilityIdentifier] ? 1 : 0;
  for (UIView *subview in view.subviews) {
    sum += [self countOfViewsWithAccessibilityIdentifier:accessibilityIdentifier
                                            fromRootView:subview];
  }
  return sum;
}

- (void)testSettingAccessibilityIdentifierAffectsExactlyOneSubview {
  // Given
  UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:1];
  item.accessibilityIdentifier = @"__i_d__";

  // When
  self.bottomNavBar.items = @[ item ];

  // Then
  XCTAssertEqual([self countOfViewsWithAccessibilityIdentifier:item.accessibilityIdentifier
                                                  fromRootView:self.bottomNavBar],
                 1);
}

- (void)testAccessibilityLabelInitialValue {
  // Given
  NSString *initialLabel = @"initialLabel";
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.accessibilityLabel = initialLabel;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];

  // When
  bar.items = @[ tabBarItem ];

  // Then
  XCTAssertEqualObjects(bar.itemViews.firstObject.accessibilityLabel, initialLabel);
}

- (void)testAccessibilityLabelValueChanged {
  // Given
  NSString *oldLabel = @"oldLabel";
  NSString *newLabel = @"newLabel";
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.accessibilityLabel = oldLabel;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
  bar.items = @[ tabBarItem ];

  // When
  tabBarItem.accessibilityLabel = newLabel;

  // Then
  XCTAssertEqualObjects(bar.itemViews.firstObject.accessibilityLabel, newLabel);
}

- (void)testAccessibilityHintInitialValue {
  // Given
  NSString *initialHint = @"initialHint";
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.accessibilityHint = initialHint;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];

  // When
  bar.items = @[ tabBarItem ];

  // Then
  MDCBottomNavigationItemView *itemView = bar.itemViews.firstObject;
  UIButton *itemViewButton = itemView.button;
  XCTAssertEqualObjects(itemViewButton.accessibilityHint, initialHint);
}

- (void)testAccessibilityHintValueChanged {
  // Given
  NSString *oldHint = @"oldHint";
  NSString *newHint = @"newHint";
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.accessibilityHint = oldHint;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
  bar.items = @[ tabBarItem ];

  // When
  tabBarItem.accessibilityHint = newHint;

  // Then
  MDCBottomNavigationItemView *itemView = bar.itemViews.firstObject;
  UIButton *itemViewButton = itemView.button;
  XCTAssertEqualObjects(itemViewButton.accessibilityHint, newHint);
}

- (void)testIsAccessibilityElementInitialValue {
  // Given
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.isAccessibilityElement = NO;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];

  // When
  bar.items = @[ tabBarItem ];

  // Then
  XCTAssert(!bar.itemViews.firstObject.isAccessibilityElement);
}

- (void)testIsAccessibilityElementValueChanged {
  // Given
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.isAccessibilityElement = NO;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
  bar.items = @[ tabBarItem ];

  // When
  tabBarItem.isAccessibilityElement = YES;

  // Then
  XCTAssert(bar.itemViews.firstObject.isAccessibilityElement);
}

- (void)testTagInitialValue {
  // Given
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.tag = 1234;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];

  // When
  bar.items = @[ tabBarItem ];

  // Then
  XCTAssertEqual(bar.itemViews.firstObject.tag, tabBarItem.tag);
}

- (void)testTagValueChanged {
  // Given
  UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:nil tag:0];
  tabBarItem.tag = 1234;
  MDCBottomNavigationBar *bar = [[MDCBottomNavigationBar alloc] init];
  bar.items = @[ tabBarItem ];

  // When
  tabBarItem.tag = 4321;

  // Then
  XCTAssertEqual(bar.itemViews.firstObject.tag, tabBarItem.tag);
}

- (void)testTitleVisibility {
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityNever;
  for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
    XCTAssertEqualWithAccuracy(itemView.label.alpha, 0.0, 0.001);
  }
  self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilityAlways;
  for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
    XCTAssertEqualWithAccuracy(itemView.label.alpha, 1.0, 0.001);
  }
  self.bottomNavBar.titleVisibility = MDCBottomNavigationBarTitleVisibilitySelected;
  self.bottomNavBar.itemViews.firstObject.selected = YES;
  self.bottomNavBar.itemViews.lastObject.selected = NO;
  XCTAssertEqualWithAccuracy(self.bottomNavBar.itemViews.firstObject.label.alpha, 1.0, 0.001);
  XCTAssertEqualWithAccuracy(self.bottomNavBar.itemViews.lastObject.label.alpha, 0.0, 0.001);
}

- (void)testDefaultElevation {
  // Then
  XCTAssertEqualWithAccuracy(self.bottomNavBar.elevation, MDCShadowElevationBottomNavigationBar,
                             0.001);
}

- (void)testCustomElevation {
  // Given
  CGFloat customElevation = 20;

  // When
  self.bottomNavBar.elevation = customElevation;

  // Then
  XCTAssertEqualWithAccuracy(self.bottomNavBar.elevation, customElevation, 0.001);
}

- (void)testDefaultShadowColor {
  // Then
  XCTAssertEqualObjects(self.bottomNavBar.shadowColor, UIColor.blackColor);
  XCTAssertTrue(
      CGColorEqualToColor(self.bottomNavBar.layer.shadowColor, UIColor.blackColor.CGColor),
      @"(%@) is not equal to (%@)", self.bottomNavBar.layer.shadowColor,
      UIColor.blackColor.CGColor);
}

- (void)testCustomShadowColor {
  // Given
  UIColor *fakeColor = UIColor.orangeColor;

  // When
  self.bottomNavBar.shadowColor = fakeColor;

  // Then
  XCTAssertEqualObjects(self.bottomNavBar.shadowColor, fakeColor);
  XCTAssertTrue(CGColorEqualToColor(self.bottomNavBar.layer.shadowColor, fakeColor.CGColor),
                @"(%@) is not equal to (%@)", self.bottomNavBar.layer.shadowColor,
                fakeColor.CGColor);
}

- (void)testViewForItemFound {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];

  // When
  self.bottomNavBar.items = @[ item1, item2 ];

  // Then
  MDCBottomNavigationItemView *viewForItem1 =
      (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item1];
  MDCBottomNavigationItemView *viewForItem2 =
      (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item2];
  XCTAssertNotEqual(viewForItem1, viewForItem2);
  XCTAssertTrue(
      [self.bottomNavBar.itemViews containsObject:viewForItem1],
      @"BottomNavBar.itemViews did not contain the view (%@) returned for UITabBarItem (%@)",
      viewForItem1, item1);
  XCTAssertTrue(
      [self.bottomNavBar.itemViews containsObject:viewForItem2],
      @"BottomNavBar.itemViews did not contain the view (%@) returned for UITabBarItem (%@)",
      viewForItem2, item2);
}

- (void)testViewForItemNotFound {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"3" image:nil tag:0];

  // When
  self.bottomNavBar.items = @[ item1, item2 ];

  // Then
  XCTAssert([self.bottomNavBar viewForItem:item3] == nil);
}

- (void)testSizeThatFitsDefaultIncludesSafeArea {
  // Given
  CGRect barFrame = CGRectMake(0, 0, 360, 56);
  MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
      [[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
  CGSize initialSize = [bottomNavBar sizeThatFits:barFrame.size];

  // When
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);

  // Then
  CGSize finalSize = [bottomNavBar sizeThatFits:barFrame.size];
  XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
                 "sizeThatFits: should not return CGSizeZero");
  XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
                NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}

- (void)testSizeThatFitsExcludesSafeArea {
  // Given
  CGRect barFrame = CGRectMake(0, 0, 360, 56);
  MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
      [[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
  CGSize initialSize = [bottomNavBar sizeThatFits:barFrame.size];

  // When
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);

  // Then
  CGSize finalSize = [bottomNavBar sizeThatFits:barFrame.size];
  XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
                 "sizeThatFits: should not return CGSizeZero");
  XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
                NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}

/*
 Tests when the bottomNavBar width is not evenly divisible by the number of items, the  difference
 between start of the frame and center of the first element and end of the frame and center of the
 last element is max one pixel
 */
- (void)testLaysoutAcrossBarEvenly {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"3" image:nil tag:0];
  UITabBarItem *item4 = [[UITabBarItem alloc] initWithTitle:@"4" image:nil tag:0];
  UITabBarItem *item5 = [[UITabBarItem alloc] initWithTitle:@"5" image:nil tag:0];
  CGFloat bottomNavBarWidth = 304;
  self.bottomNavBar.frame = CGRectMake(0, 0, bottomNavBarWidth, 56);

  // When
  self.bottomNavBar.items = @[ item1, item2, item3, item4, item5 ];
  [self.bottomNavBar layoutIfNeeded];

  // Then
  MDCBottomNavigationItemView *viewForItem1 =
      (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item1];
  MDCBottomNavigationItemView *viewForItem5 =
      (MDCBottomNavigationItemView *)[self.bottomNavBar viewForItem:item5];
  XCTAssertEqualWithAccuracy(viewForItem1.center.x, bottomNavBarWidth - viewForItem5.center.x, 1.0);
}

#pragma mark - Autolayout support

- (void)testIntrinsicContentSizeIgnoresSafeArea {
  // Given
  CGRect barFrame = CGRectMake(0, 0, 360, 56);
  MDCSafeAreaCustomizingBottomNavigationBar *bottomNavBar =
      [[MDCSafeAreaCustomizingBottomNavigationBar alloc] initWithFrame:barFrame];
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsZero;
  CGSize initialSize = [bottomNavBar intrinsicContentSize];

  // When
  bottomNavBar.test_safeAreaInsets = UIEdgeInsetsMake(20, 20, 20, 20);

  // Then
  CGSize finalSize = [bottomNavBar intrinsicContentSize];
  XCTAssertFalse(CGSizeEqualToSize(finalSize, CGSizeZero),
                 "intrinsicContentSize should not return CGSizeZero");
  XCTAssertTrue(CGSizeEqualToSize(finalSize, initialSize), @"(%@) is not equal to (%@)",
                NSStringFromCGSize(finalSize), NSStringFromCGSize(initialSize));
}

#pragma mark - Property propagation

- (void)testTitlesNumberOfLinesPassedToViewsBeforeItemsAssigned {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  self.bottomNavBar.alignment = MDCBottomNavigationBarAlignmentJustified;

  // When
  self.bottomNavBar.titlesNumberOfLines = 7;
  self.bottomNavBar.items = @[ item1, item2 ];

  // Then
  for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
    XCTAssertEqual(itemView.titleNumberOfLines, self.bottomNavBar.titlesNumberOfLines);
  }
}

- (void)testTitlesNumberOfLinesPassedToViewsAfterItemsAssigned {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  self.bottomNavBar.alignment = MDCBottomNavigationBarAlignmentJustified;

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  self.bottomNavBar.titlesNumberOfLines = 7;

  // Then
  for (MDCBottomNavigationItemView *itemView in self.bottomNavBar.itemViews) {
    XCTAssertEqual(itemView.titleNumberOfLines, self.bottomNavBar.titlesNumberOfLines);
  }
}

- (void)testItemForPointInsideItemViewReturnsCorrespondingItem {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  [self.bottomNavBar layoutIfNeeded];
  UIView *view1 = [self.bottomNavBar viewForItem:item1];
  UIView *view2 = [self.bottomNavBar viewForItem:item2];
  UITabBarItem *result1 = [self.bottomNavBar tabBarItemForPoint:view1.center];
  UITabBarItem *result2 = [self.bottomNavBar tabBarItemForPoint:view2.center];

  // Then
  XCTAssertEqualObjects(result1, item1);
  XCTAssertEqualObjects(result2, item2);
}

- (void)testItemForPointOutsideNavigationBarReturnsNil {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  CGFloat navBarHeight = 56;
  self.bottomNavBar.frame = CGRectMake(0, 0, 320, navBarHeight);
  CGPoint testPoint = CGPointMake(0, navBarHeight + 10);

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  [self.bottomNavBar layoutIfNeeded];
  UITabBarItem *result = [self.bottomNavBar tabBarItemForPoint:testPoint];

  // Then
  XCTAssertNil(result);
}

- (void)testItemForPointInsideNavigationBarOutsideItemViewReturnsNil {
  // Given
  UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
  UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
  CGFloat navBarHeight = 200;
  self.bottomNavBar.frame = CGRectMake(0, 0, 320, navBarHeight);
  CGPoint testPoint = CGPointMake(0, navBarHeight - 10);

  // When
  self.bottomNavBar.items = @[ item1, item2 ];
  [self.bottomNavBar layoutIfNeeded];
  UITabBarItem *result = [self.bottomNavBar tabBarItemForPoint:testPoint];

  // Then
  XCTAssertNil(result);
}

- (void)testItemForPointInsideNavigationBarNoTabBarItemsReturnsNil {
  // Given
  self.bottomNavBar.frame = CGRectMake(0, 0, 320, 56);

  // When
  [self.bottomNavBar layoutIfNeeded];
  UITabBarItem *result = [self.bottomNavBar tabBarItemForPoint:self.bottomNavBar.center];

  // Then
  XCTAssertNil(result);
}

- (NSInteger)countOfButtonsWithAccessibilityHint:(NSString *)accessibilityHint
                                    fromRootView:(UIView *)view {
  BOOL foundMatchingElement = NO;
  foundMatchingElement = ([view isKindOfClass:[UIButton class]] &&
                          [view.accessibilityHint isEqualToString:accessibilityHint]);
  NSInteger count = foundMatchingElement ? 1 : 0;
  for (UIView *subview in view.subviews) {
    count += [self countOfButtonsWithAccessibilityHint:accessibilityHint fromRootView:subview];
  }
  return count;
}

- (void)testSettingAccessibilityHintPropagatesToOneUIButtonSubview {
  // Given
  UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:9];
  item.accessibilityHint = @"__h_i_n_t__";

  // When
  self.bottomNavBar.items = @[ item ];

  // Then
  XCTAssertEqual([self countOfButtonsWithAccessibilityHint:item.accessibilityHint
                                              fromRootView:self.bottomNavBar],
                 1);
}

#pragma mark - traitCollectionDidChangeBlock

- (void)testTraitCollectionDidChangeBlockCalledWhenTraitCollectionChanges {
  // Given
  __block MDCBottomNavigationBar *passedBottomNavigationBar = nil;
  __block UITraitCollection *passedTraitCollection = nil;
  XCTestExpectation *expectation =
      [self expectationWithDescription:@"Called traitCollectionDidChangeBlock"];
  UITraitCollection *testTraitCollection = [UITraitCollection traitCollectionWithDisplayScale:77];
  void (^block)(MDCBottomNavigationBar *_Nonnull, UITraitCollection *_Nullable) = ^void(
      MDCBottomNavigationBar *bottomNavigationBar, UITraitCollection *previousTraitCollection) {
    passedBottomNavigationBar = bottomNavigationBar;
    passedTraitCollection = previousTraitCollection;
    [expectation fulfill];
  };
  self.bottomNavBar.traitCollectionDidChangeBlock = block;

  // When
  [self.bottomNavBar traitCollectionDidChange:testTraitCollection];
  [self waitForExpectations:@[ expectation ] timeout:1];

  // Then
  XCTAssertEqual(passedBottomNavigationBar, self.bottomNavBar);
  XCTAssertEqual(passedTraitCollection, testTraitCollection);
}

#pragma mark - MaterialElevation

- (void)testDefaultValueForOverrideBaseElevationIsNegative {
  // Given
  MDCBottomNavigationBar *bottomNavigationBar = [[MDCBottomNavigationBar alloc] init];

  // Then
  XCTAssertLessThan(bottomNavigationBar.mdc_overrideBaseElevation, 0);
}

- (void)testSettingOverrideBaseElevationReturnsSetValue {
  // Given
  CGFloat expectedBaseElevation = 99;

  // When
  self.bottomNavBar.mdc_overrideBaseElevation = expectedBaseElevation;

  // Then
  XCTAssertEqualWithAccuracy(self.bottomNavBar.mdc_overrideBaseElevation, expectedBaseElevation,
                             0.001);
}

- (void)testCurrentElevationMatchesElevationWhenElevationChanges {
  // When
  self.bottomNavBar.elevation = MDCShadowElevationQuickEntryResting;

  // Then
  XCTAssertEqualWithAccuracy(self.bottomNavBar.mdc_currentElevation, self.bottomNavBar.elevation,
                             0.001);
}

- (void)testElevationDidChangeBlockCalledWhenElevationChangesValue {
  // Given
  self.bottomNavBar.elevation = 5;
  __block BOOL blockCalled = NO;
  self.bottomNavBar.mdc_elevationDidChangeBlock = ^(id<MDCElevatable> _, CGFloat elevation) {
    blockCalled = YES;
  };

  // When
  self.bottomNavBar.elevation = self.bottomNavBar.elevation + 1;

  // Then
  XCTAssertTrue(blockCalled);
}

- (void)testElevationDidChangeBlockNotCalledWhenElevationIsSetWithoutChangingValue {
  // Given
  self.bottomNavBar.elevation = 5;
  __block BOOL blockCalled = NO;
  self.bottomNavBar.mdc_elevationDidChangeBlock = ^(id<MDCElevatable> _, CGFloat elevation) {
    blockCalled = YES;
  };

  // When
  self.bottomNavBar.elevation = self.bottomNavBar.elevation;

  // Then
  XCTAssertFalse(blockCalled);
}

- (void)testSettingBarHeightPropertyUpdatesIntrinsicContentSizeOfBar {
  // Given
  CGFloat barHeight = 20;

  // When
  self.bottomNavBar.barHeight = barHeight;

  // Then
  XCTAssertEqual(self.bottomNavBar.intrinsicContentSize.height, barHeight);
}

- (void)testSettingBarHeightPropertyUpdatesSizeThatFitsOfBar {
  // Given
  CGFloat barHeight = 20;

  // When
  self.bottomNavBar.barHeight = barHeight;

  // Then
  XCTAssertEqual([self.bottomNavBar sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].height,
                 barHeight);
}

#pragma mark - UILargeContentViewerItem

#if MDC_AVAILABLE_SDK_IOS(13_0)
/** Tests the large content  title when the title should not contain a badge. */
- (void)testLargeContentTitle {
  if (@available(iOS 13.0, *)) {
    // Given.
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0];
    self.bottomNavBar.items = @[ item ];

    // When.
    NSString *largeContentTitle = self.bottomNavBar.itemViews.firstObject.largeContentTitle;

    // Then.
    XCTAssertEqualObjects(largeContentTitle, item.title);
  }
}

/**
 Tests the large content image is the @c image property when no @c largeContentImage is
 specified.
 */
- (void)testLargeContentImageWithDefaultImage {
  if (@available(iOS 13.0, *)) {
    // Given.
    UIImage *image = [[UIImage alloc] init];
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:image tag:0];
    item.largeContentSizeImage = image;
    self.bottomNavBar.items = @[ item ];

    // When.
    UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;

    // Then.
    XCTAssertEqualObjects(largeContentImage, image);
  }
}

/** Tests the large content item's image is the @c largeContentSizeImage . */
- (void)testLargeContentImageWithTabBarLargeContentImage {
  if (@available(iOS 13.0, *)) {
    // Given.
    UIImage *image = [[UIImage alloc] init];
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
    item.largeContentSizeImage = image;
    self.bottomNavBar.items = @[ item ];

    // When.
    UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;

    // Then.
    XCTAssertEqualObjects(largeContentImage, image);
  }
}

/** Tests the large content image insets are set. */
- (void)testLargeContentImageInsets {
  if (@available(iOS 13.0, *)) {
    // Given.
    UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
    item.largeContentSizeImageInsets = insets;
    self.bottomNavBar.items = @[ item ];

    // When.
    UIEdgeInsets largeImageInsets = self.bottomNavBar.itemViews.firstObject.largeContentImageInsets;

    // Then.
    XCTAssertEqual(largeImageInsets.bottom, insets.bottom);
    XCTAssertEqual(largeImageInsets.top, insets.top);
    XCTAssertEqual(largeImageInsets.left, insets.left);
    XCTAssertEqual(largeImageInsets.right, insets.right);
  }
}

/**
 Tests the large content item's image is updated when the tab bar's @c largeContentSizeImage is
 updated.
 */
- (void)testLargeContentImageUpdatedWhenTabBarPropertyUpdates {
  if (@available(iOS 13.0, *)) {
    // Given.
    UIImage *image = [[UIImage alloc] init];
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
    self.bottomNavBar.items = @[ item ];

    // When.
    item.largeContentSizeImage = image;
    UIImage *largeContentImage = self.bottomNavBar.itemViews.firstObject.largeContentImage;

    // Then.
    XCTAssertEqualObjects(largeContentImage, image);
  }
}

/**
 Tests the large content inets are update when the tab bar's @c largeContentSizeImageInsets are
 updated.
 */
- (void)testLargeContentInsetUpdatedWhenTabBarPropertyUpdates {
  if (@available(iOS 13.0, *)) {
    // Given.
    UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:nil image:nil tag:0];
    self.bottomNavBar.items = @[ item ];

    // When.
    item.largeContentSizeImageInsets = insets;
    UIEdgeInsets largeContentInsets =
        self.bottomNavBar.itemViews.firstObject.largeContentImageInsets;

    // Then.
    XCTAssertEqual(largeContentInsets.left, insets.left);
    XCTAssertEqual(largeContentInsets.bottom, insets.bottom);
    XCTAssertEqual(largeContentInsets.right, insets.right);
    XCTAssertEqual(largeContentInsets.top, insets.top);
  }
}

/**
 Tests the last shown large content item is still returned when the interaction touch point is
 still within the bounds of the navigation bar.
 */
- (void)testLargeContentViewerInteractionWhenItemIsSelectedThenDeselectedButStillInNavBarBounds {
  if (@available(iOS 13.0, *)) {
    // Given.
    NSString *title1 = @"Title1";
    UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:title1 image:nil tag:0];
    self.bottomNavBar.items = @[ item1 ];
    self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
    [self.bottomNavBar layoutIfNeeded];
    UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
    self.continueAfterFailure = NO;

    // When/Then.
    XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
                                                                                    itemAtPoint:)]);
    CGPoint itemViewOrigin = self.bottomNavBar.itemViews.firstObject.frame.origin;
    id<UILargeContentViewerItem> largeContentItem =
        [self.bottomNavBar largeContentViewerInteraction:interaction itemAtPoint:itemViewOrigin];
    XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1);

    largeContentItem = [self.bottomNavBar largeContentViewerInteraction:interaction
                                                            itemAtPoint:CGPointZero];
    XCTAssertEqualObjects(largeContentItem.largeContentTitle, title1);
  }
}

/**
 Tests the large content item is nil when the touch point is outside the navigation bar bounds.
 */
- (void)testLargeContentViewerInteractionWhenPointIsOutSideNavBarBounds {
  if (@available(iOS 13.0, *)) {
    // Given.
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title1" image:nil tag:0];
    self.bottomNavBar.items = @[ item ];
    self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
    [self.bottomNavBar layoutIfNeeded];
    UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
    self.continueAfterFailure = NO;

    // When/Then.
    XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
                                                                                    itemAtPoint:)]);
    CGPoint pointOutsideNavBar = CGPointMake(-1, -1);

    id<UILargeContentViewerItem> largeContentItem =
        [self.bottomNavBar largeContentViewerInteraction:interaction
                                             itemAtPoint:pointOutsideNavBar];
    XCTAssertNil(largeContentItem);
  }
}

/** Tests the last shown large item is reset after an interaction ends. */
- (void)testLargeContentViewerInteractionResetsAfterEnding {
  if (@available(iOS 13.0, *)) {
    // Given.
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"Title1" image:nil tag:0];
    self.bottomNavBar.items = @[ item ];
    self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
    [self.bottomNavBar layoutIfNeeded];
    UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
    self.continueAfterFailure = NO;

    // When/Then.
    XCTAssertTrue([self.bottomNavBar respondsToSelector:@selector(largeContentViewerInteraction:
                                                                                    itemAtPoint:)]);

    CGPoint pointInsideFirstItem = self.bottomNavBar.itemViews.firstObject.frame.origin;
    id<UILargeContentViewerItem> endItem =
        [self.bottomNavBar largeContentViewerInteraction:interaction
                                             itemAtPoint:pointInsideFirstItem];

    XCTAssertTrue([self.bottomNavBar
        respondsToSelector:@selector(largeContentViewerInteraction:didEndOnItem:atPoint:)]);
    [self.bottomNavBar largeContentViewerInteraction:interaction
                                        didEndOnItem:endItem
                                             atPoint:CGPointZero];

    id<UILargeContentViewerItem> itemAfterReset =
        [self.bottomNavBar largeContentViewerInteraction:interaction itemAtPoint:CGPointZero];
    if (@available(iOS 13, *)) {
      // TODO(b/184162265): Evaluate why this behavior is different on iOS 13+.
      XCTAssertNotNil(itemAfterReset);
    } else {
      XCTAssertNil(itemAfterReset);
    }
  }
}
#endif  // MDC_AVAILABLE_SDK_IOS(13_0)

/**
Tests the @c itemsHorizontalPadding property
 */
- (void)testItemsHorizontalPaddingProperty {
  // Given
  NSString *title = @"Title";
  UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:title image:nil tag:0];
  self.bottomNavBar.items = @[ item ];
  self.bottomNavBar.frame = CGRectMake(0, 0, 350, 125);
  [self.bottomNavBar sizeToFit];

  // When
  [self.bottomNavBar setNeedsLayout];
  [self.bottomNavBar layoutIfNeeded];

  MDCBottomNavigationItemView *itemView = [self.bottomNavBar.itemViews firstObject];
  CGRect expectedItemViewFrameWithDefaultItemsHorizontalPadding =
      CGRectInset(self.bottomNavBar.frame, 0, 0);
  CGRect actualItemViewFrameWithDefaultItemsHorizontalPadding = itemView.frame;

  self.bottomNavBar.itemsHorizontalPadding = 0;
  [self.bottomNavBar setNeedsLayout];
  [self.bottomNavBar layoutIfNeeded];
  CGRect expectedItemViewFrameWithZeroItemsHorizontalPadding = self.bottomNavBar.frame;
  CGRect actualItemViewFrameWithZeroItemsHorizontalPadding = itemView.frame;

  // Then
  XCTAssertTrue(CGRectEqualToRect(expectedItemViewFrameWithDefaultItemsHorizontalPadding,
                                  actualItemViewFrameWithDefaultItemsHorizontalPadding));
  XCTAssertTrue(CGRectEqualToRect(expectedItemViewFrameWithZeroItemsHorizontalPadding,
                                  actualItemViewFrameWithZeroItemsHorizontalPadding));
}

#ifdef __IPHONE_13_4
/**
 Verifies that MDCBottomNavigation generates a non-nil UIPointerStyle for its item views.
 */
- (void)testPointerStyleIsNonNilWithMDCBottomNavigationItemView {
  if (@available(iOS 13.4, *)) {
    // Given
    // Pointer interactions require that the view being interacted with is in a UIWindow
    [self createWindowWithView:self.bottomNavBar];
    UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:kTestItemTitleText image:nil tag:0];
    [self.bottomNavBar setItems:@[ item ]];

    MDCBottomNavigationItemView *firstItemView = self.bottomNavBar.itemViews.firstObject;
    UIPointerInteraction *interaction = firstItemView.interactions.firstObject;
    XCTAssertEqual(interaction.delegate, self.bottomNavBar);
    UIPointerRegion *region = [UIPointerRegion regionWithRect:firstItemView.frame identifier:nil];
    UIPointerStyle *pointerStyle = [self.bottomNavBar pointerInteraction:interaction
                                                          styleForRegion:region];

    // Then
    XCTAssertNotNil(pointerStyle);
  }
}

/**
 Verifies that MDCBottomNavigation does not generate a UIPointerStyle for a non-item view.
 */
- (void)testPointerStyleIsNilWithNonMDCBottomNavigationItemView {
  if (@available(iOS 13.4, *)) {
    // Given
    // Pointer interactions require that the view being interacted with is in a UIWindow
    [self createWindowWithView:self.bottomNavBar];
    UIView *nonBottomNavigationItemView = [[UIView alloc] init];
    UIPointerInteraction *interaction =
        [[UIPointerInteraction alloc] initWithDelegate:self.bottomNavBar];
    [nonBottomNavigationItemView addInteraction:interaction];
    UIPointerRegion *region = [UIPointerRegion regionWithRect:nonBottomNavigationItemView.frame
                                                   identifier:nil];
    UIPointerStyle *pointerStyle = [self.bottomNavBar pointerInteraction:interaction
                                                          styleForRegion:region];

    // Then
    XCTAssertNil(pointerStyle);
  }
}
#endif

#pragma mark - Helpers

- (void)createWindowWithView:(UIView *)view {
  UIWindow *window = [[UIWindow alloc] init];
  [window addSubview:view];
}

@end
